/*
 *  ELF/Mach-O object file loading
 *
 *  Copyright (c) 2006 Pierre d'Herbemont
 *  Copyright (c) 2010-2011 Tobias Platen
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
 */
#include "dyld_private.h"

void bswap_fh(struct fat_header *fh)
{
    fh->magic = htonl(fh->magic);
    fh->nfat_arch =  htonl(fh->nfat_arch);

}

void bswap_fa(struct fat_arch *fa)
{
    fa->cputype=htonl(fa->cputype);
    fa->cpusubtype=htonl(fa->cpusubtype);
    fa->offset=htonl(fa->offset);
    fa->size=htonl(fa->size);
    fa->align=htonl(fa->align);
}


void qerror( char *fmt, ... ) {
    va_list args;
    va_start( args, fmt );
    fprintf( stderr, "Error: " );
    vfprintf( stderr, fmt, args );
    fprintf( stderr, "\n" );
    va_end( args );
    exit( 1 );
}


ImageLoader* load_object(const char *filename)
{
    struct mach_header mach_hdr;	
    int need_bswap = 0;
    int entry_point = 0;
    int dyld_command = 0; 
    int slide, mmapfixed;
    int fd;
    struct load_command *lcmds, *lc;
    int is_fat = 0;
    unsigned int i, magic;
    int mach_hdr_pos = 0;
    struct symtab_command *    symtabcmd = 0;

    DYLD_DEBUG(printf("loading %s\n",filename));
    void* dl=dlopen(filename,RTLD_LAZY);
    if(dl) return new ImageLoaderElf(dl,filename,"");
    

    
    //info->path=filename;save filename later
	
    	
    
    fd = open(filename, O_RDONLY);
    if (fd < 0) return NULL;
      //  qerror("can't open file '%s'", filename);
//	goto error;
    
    /* Read magic header.  */
    if (read(fd, &magic, sizeof (magic)) != sizeof (magic))
        qerror("unable to read Magic of '%s'", filename);

    /* Check Mach identification.  */
    if(magic == HeaderMagic32)
    {
        is_fat = 0;
        need_bswap = 0;
    } else if (magic == HeaderMagic32Swapped)
    {
        is_fat = 0;
        need_bswap = 1;
    } else if (magic == UniversalMagic)
    {
        is_fat = 1;
        need_bswap = 0;
    } else if (magic == UniversalMagicSwapped)
    {
        is_fat = 1;
        need_bswap = 1;
    }
    else
        qerror("Not a Mach-O file.", filename);
    //throw an exception ?	

    int fa_offset=0;
    int fa_size=0;
    if(is_fat)
    {
        int found = 0;
        struct fat_header fh;
        struct fat_arch *fa;

        lseek(fd, 0, SEEK_SET);

        /* Read Fat header.  */
        if (read(fd, &fh, sizeof (fh)) != sizeof (fh))
            qerror("unable to read file header");

        if(need_bswap)
            bswap_fh(&fh);

        /* Read Fat Arch.  */
        fa = (llvm::MachO::fat_arch*)malloc(sizeof(struct fat_arch)*fh.nfat_arch);

        if (read(fd, fa, sizeof(struct fat_arch)*fh.nfat_arch) != sizeof(struct fat_arch)*fh.nfat_arch)
            qerror("unable to read file header");

        for( i = 0; i < fh.nfat_arch; i++, fa++)
        {
            //if(need_bswap)
            bswap_fa(fa);
            if(fa->cputype == TARGET_CPU_TYPE)
            {
                mach_hdr_pos = fa->offset;
                lseek(fd, mach_hdr_pos, SEEK_SET);

                fa_offset=fa->offset;
		fa_size=fa->size;

                /* Read Mach header.  */

                if (read(fd, &mach_hdr, sizeof(struct mach_header)) != sizeof (struct mach_header))
                    qerror("unable to read file header");

                if(mach_hdr.magic == HeaderMagic32)
                    need_bswap = 0;
                else if (mach_hdr.magic == HeaderMagic32Swapped)
                    need_bswap = 1;
                else
                    qerror("Invalid mach header in Fat Mach-O File");
                found = 1;
                break;
            }
        }
        if(!found)
            qerror("%s: No %s CPU found in FAT Header", filename, TARGET_CPU_NAME);
    }
    else
    {
        lseek(fd, 0, SEEK_SET);
        /* Read Mach header */
        if (read(fd, &mach_hdr, sizeof (mach_hdr)) != sizeof (mach_hdr))
            qerror("%s: unable to read file header", filename);
	
	

    }

    if ((mach_hdr.cputype) != TARGET_CPU_TYPE)
        qerror("%s: Unsupported CPU 0x%x (only 0x%x(%s) supported)", filename, mach_hdr.cputype, TARGET_CPU_TYPE, TARGET_CPU_NAME);


    switch(mach_hdr.filetype)
    {
    case HeaderFileTypeExecutable:
    case HeaderFileTypeDynamicShlib:
    case HeaderFileTypeBundle:
	break;
    default:
        qerror("%s: Unsupported Mach type (0x%x)", filename, mach_hdr.filetype);
    }

    /* read segment headers */
    lcmds = (llvm::MachO::load_command*) malloc(mach_hdr.sizeofcmds);

    if(read(fd, lcmds, mach_hdr.sizeofcmds) != mach_hdr.sizeofcmds)
        qerror("%s: unable to read load_command", filename);
    slide = 0;
    mmapfixed = 0;
    //int segaddr;
    char* dumpseg=getenv("DYLD_DUMPSEG");
    bool compressed=false;
    //int j;
    int nsegments=0;	
    //see sniffLoadCommands in http://www.opensource.apple.com/source/dyld/dyld-132.13/src/ImageLoaderMachO.cpp
    for(i=0, lc = lcmds; i < (mach_hdr.ncmds) ; i++)
    {
	if(lc->cmd==LoadCommandSegment32) nsegments++;
	else
	if(lc->cmd==LoadCommandDynamicLinkerInfo || lc->cmd==LoadCommandDynamicLinkerInfoOnly) compressed=true;
        lc = (struct load_command*)((int)(lc)+(lc->cmdsize));
    }
    struct segment_command** segments=(struct segment_command**)malloc(sizeof(struct segment_command*)*nsegments);
    int j=0;
    for(i=0, lc = lcmds; i < (mach_hdr.ncmds) ; i++)
    {
	if(lc->cmd==LoadCommandSegment32)
	{
	  segments[j]=(struct segment_command*)lc;
	  j++;
	}
        lc = (struct load_command*)((int)(lc)+(lc->cmdsize));
    }
    DYLD_DEBUG(printf("load commands: %i compressed: %i\n",nsegments,compressed));
    if(compressed) qerror("snow leopard not supported yet");
    return new ImageLoaderMachO(fd,fa_offset,fa_size,nsegments,segments,mach_hdr.filetype,mach_hdr.flags);		
}
